#ifndef SD_BALL_HPP
#define SD_BALL_HPP

#include "config.hpp"
#include <iostream>

namespace MT {

// ball = a hyperball
template<typename _Scalar, int _Rows>
struct ball
{
    bool empty; // true if the ball is empty -- i.e. its center is invalid
    _Scalar r; // radius
    Eigen::Matrix<_Scalar, _Rows, 1> c; // center

#ifdef USE_BOOST_SERIALIZATION
    friend class boost::serialization::access;
    template<class Archive>
    inline void serialize(Archive & ar, const unsigned int version)
    {
        ar & BOOST_SERIALIZATION_NVP(empty);
        if(!empty)
        {
            ar & BOOST_SERIALIZATION_NVP(r) & BOOST_SERIALIZATION_NVP(c);
        }
    }
#endif

    // returns true if the ball is empty
    bool is_empty() const { return empty; }

    // returns true if the ball is not empty but it has zero radius
    bool is_radius_zero() const { return iszero(r); }

    // returns the dimensionality of the ball
    //   = 00 if empty or zero radius
    //   = c.rows otherwise
    int dim() const { return (is_empty() || is_radius_zero())? 0: c.rows; }

    // constructors
    ball(int D=_Rows) : empty(true), r(0) { if(D == Eigen::Dynamic) D = 3; c.resize(D, 1); }
    ball(Eigen::Matrix<_Scalar, _Rows, 1> const &c, _Scalar r) : empty(false), r(r) { this->c = c; }
    ball(ball const &inst) { *this = inst; }

    // assignment
    ball &operator=(ball const &inst)
    {
        empty = inst.empty;
        r = inst.r;
        c = inst.c;
        return *this;
    }

    // compares the ball with a point
    // returns:
    //   -1 if the point is strictly inside the ball
    //    0 if the point is on the ball
    //   +1 if the point is strictly outside the ball
    int compare(Eigen::Matrix<_Scalar, _Rows, 1> const &pt)
    {
        if(is_empty()) return +1;
        _Scalar d = (pt-c).norm()-r;
        if(iszero(d)) return 0;
        if(d > 0) return +1;
        return -1;
    }

    // checks if the current ball intersects with another ball
    // Here, intersection means there exists at least a common point.
    // returns:
    //   -1 if they don't intersect
    //    0 if they touch on their boundaries
    //   +1 if they strictly overlap
    int intersects_with(ball const &inst)
    {
        if(is_empty() || inst.is_empty()) return -1;
        _Scalar d = (c-inst.c).norm() - r - inst.r;
        if(iszero(d)) return 0;
        if(d < 0) return -1;
        return +1;
    }
};

template<typename _Scalar, int _Rows>
std::ostream &operator <<(std::ostream &ofs, ball<_Scalar, _Rows> const &b)
{
    ofs << "ball(";
    if(b.is_empty()) ofs << "empty";
    else {
        ofs << "c=(";
        int i, N = b.c.rows();
        for(i = 0; i < N; ++i) ofs << b.c[i] << ",";
        ofs << "), r=" << b.r;
    }
    ofs << ")";
    return ofs;
}


} // namespace MT

#endif
